﻿using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using HandyControl.Data;
using HandyControl.Tools;

namespace HandyControl.Interactivity;

/// <summary>
///     鼠标拖动行为，该类是反编译微软的System.Windows.Interactivity程序集得到的，并对其做了些扩展
/// </summary>
internal class MouseDragElementBehaviorEx : Behavior<FrameworkElement>
{
    public static readonly DependencyProperty XProperty = DependencyProperty.Register(nameof(X), typeof(double),
        typeof(MouseDragElementBehaviorEx), new PropertyMetadata(double.NaN, OnXChanged));

    public static readonly DependencyProperty YProperty = DependencyProperty.Register(nameof(Y), typeof(double),
        typeof(MouseDragElementBehaviorEx), new PropertyMetadata(double.NaN, OnYChanged));

    public static readonly DependencyProperty ConstrainToParentBoundsProperty =
        DependencyProperty.Register(nameof(ConstrainToParentBounds), typeof(bool),
            typeof(MouseDragElementBehaviorEx), new PropertyMetadata(ValueBoxes.FalseBox, OnConstrainToParentBoundsChanged));

    private Transform _cachedRenderTransform;
    private Point _relativePosition;
    private bool _settingPosition;

    /// <summary>
    ///     是否固定住Y轴
    /// </summary>
    public bool LockY { get; set; }

    /// <summary>
    ///     是否固定住X轴
    /// </summary>
    public bool LockX { get; set; }

    public double X
    {
        get => (double) GetValue(MouseDragElementBehavior.XProperty);
        set => SetValue(MouseDragElementBehavior.XProperty, value);
    }

    public double Y
    {
        get => (double) GetValue(MouseDragElementBehavior.YProperty);
        set => SetValue(MouseDragElementBehavior.YProperty, value);
    }

    public bool ConstrainToParentBounds
    {
        get => (bool) GetValue(MouseDragElementBehavior.ConstrainToParentBoundsProperty);
        set => SetValue(MouseDragElementBehavior.ConstrainToParentBoundsProperty, ValueBoxes.BooleanBox(value));
    }

    private Rect ElementBounds
    {
        get
        {
            var layoutRect = ArithmeticHelper.GetLayoutRect(AssociatedObject);
            return new Rect(new Point(0.0, 0.0), new Size(layoutRect.Width, layoutRect.Height));
        }
    }

    private FrameworkElement ParentElement => AssociatedObject.Parent as FrameworkElement;

    private UIElement RootElement
    {
        get
        {
            DependencyObject reference = AssociatedObject;
            for (var dependencyObject = reference;
                 dependencyObject != null;
                 dependencyObject = VisualTreeHelper.GetParent(reference))
                reference = dependencyObject;
            return reference as UIElement;
        }
    }

    private Transform RenderTransform
    {
        get
        {
            if (_cachedRenderTransform == null ||
                !ReferenceEquals(_cachedRenderTransform, AssociatedObject.RenderTransform))
                RenderTransform = CloneTransform(AssociatedObject.RenderTransform);
            return _cachedRenderTransform;
        }
        set
        {
            if (Equals(_cachedRenderTransform, value))
                return;
            _cachedRenderTransform = value;
            AssociatedObject.RenderTransform = value;
        }
    }

    public event MouseEventHandler DragBegun;

    public event MouseEventHandler Dragging;

    public event MouseEventHandler DragFinished;

    private static void OnXChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        var dragElementBehavior = (MouseDragElementBehaviorEx) sender;
        dragElementBehavior.UpdatePosition(new Point((double) args.NewValue, dragElementBehavior.Y));
    }

    private static void OnYChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        var dragElementBehavior = (MouseDragElementBehaviorEx) sender;
        dragElementBehavior.UpdatePosition(new Point(dragElementBehavior.X, (double) args.NewValue));
    }

    private static void OnConstrainToParentBoundsChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        var dragElementBehavior = (MouseDragElementBehaviorEx) sender;
        dragElementBehavior.UpdatePosition(new Point(dragElementBehavior.X, dragElementBehavior.Y));
    }

    private void UpdatePosition(Point point)
    {
        if (_settingPosition || AssociatedObject == null)
            return;
        var transformOffset = GetTransformOffset(AssociatedObject.TransformToVisual(RootElement));
        ApplyTranslation(double.IsNaN(point.X) ? 0.0 : point.X - transformOffset.X,
            double.IsNaN(point.Y) ? 0.0 : point.Y - transformOffset.Y);
    }

    private void ApplyTranslation(double x, double y)
    {
        if (ParentElement == null)
            return;
        var point = TransformAsVector(RootElement.TransformToVisual(ParentElement), x, y);
        x = point.X;
        y = point.Y;
        if (ConstrainToParentBounds)
        {
            var parentElement = ParentElement;
            var rect1 = new Rect(0.0, 0.0, parentElement.ActualWidth, parentElement.ActualHeight);
            var rect2 = AssociatedObject.TransformToVisual(parentElement).TransformBounds(ElementBounds);
            rect2.X += x;
            rect2.Y += y;
            if (!RectContainsRect(rect1, rect2))
            {
                if (rect2.X < rect1.Left)
                {
                    var num = rect2.X - rect1.Left;
                    x -= num;
                }
                else if (rect2.Right > rect1.Right)
                {
                    var num = rect2.Right - rect1.Right;
                    x -= num;
                }
                if (rect2.Y < rect1.Top)
                {
                    var num = rect2.Y - rect1.Top;
                    y -= num;
                }
                else if (rect2.Bottom > rect1.Bottom)
                {
                    var num = rect2.Bottom - rect1.Bottom;
                    y -= num;
                }
            }
        }
        ApplyTranslationTransform(x, y);
    }

    internal void ApplyTranslationTransform(double x, double y)
    {
        var renderTransform = RenderTransform;
        var translateTransform = renderTransform as TranslateTransform;
        if (translateTransform == null)
        {
            var matrixTransform = renderTransform as MatrixTransform;
            if (renderTransform is TransformGroup transformGroup1)
            {
                if (transformGroup1.Children.Count > 0)
                    translateTransform =
                        transformGroup1.Children[transformGroup1.Children.Count - 1] as TranslateTransform;
                if (translateTransform == null)
                {
                    translateTransform = new TranslateTransform();
                    transformGroup1.Children.Add(translateTransform);
                }
            }
            else
            {
                if (matrixTransform != null)
                {
                    var matrix = matrixTransform.Matrix;
                    //在该处对微软的类进行了修改
                    if (!LockX)
                    {
                        matrix.OffsetX += x;
                    }
                    if (!LockY)
                    {
                        matrix.OffsetY += y;
                    }
                    //修改结束
                    RenderTransform = new MatrixTransform
                    {
                        Matrix = matrix
                    };
                    return;
                }
                var transformGroup2 = new TransformGroup();
                translateTransform = new TranslateTransform();
                if (renderTransform != null)
                    transformGroup2.Children.Add(renderTransform);
                transformGroup2.Children.Add(translateTransform);
                RenderTransform = transformGroup2;
            }
        }
        //在该处对微软的类进行了修改
        if (!LockX)
        {
            translateTransform.X += x;
        }
        if (!LockY)
        {
            translateTransform.Y += y;
        }
        //修改结束
    }

    internal static Transform CloneTransform(Transform transform)
    {
        if (transform == null)
            return null;
        if (transform is ScaleTransform scaleTransform)
            return new ScaleTransform
            {
                CenterX = scaleTransform.CenterX,
                CenterY = scaleTransform.CenterY,
                ScaleX = scaleTransform.ScaleX,
                ScaleY = scaleTransform.ScaleY
            };
        if (transform is RotateTransform rotateTransform)
            return new RotateTransform
            {
                Angle = rotateTransform.Angle,
                CenterX = rotateTransform.CenterX,
                CenterY = rotateTransform.CenterY
            };
        if (transform is SkewTransform skewTransform)
            return new SkewTransform
            {
                AngleX = skewTransform.AngleX,
                AngleY = skewTransform.AngleY,
                CenterX = skewTransform.CenterX,
                CenterY = skewTransform.CenterY
            };
        if (transform is TranslateTransform translateTransform)
            return new TranslateTransform
            {
                X = translateTransform.X,
                Y = translateTransform.Y
            };
        if (transform is MatrixTransform matrixTransform)
            return new MatrixTransform
            {
                Matrix = matrixTransform.Matrix
            };
        if (!(transform is TransformGroup transformGroup1))
            return null;
        var transformGroup2 = new TransformGroup();
        foreach (var child in transformGroup1.Children)
            transformGroup2.Children.Add(CloneTransform(child));
        return transformGroup2;
    }

    private void UpdatePosition()
    {
        var transformOffset = GetTransformOffset(AssociatedObject.TransformToVisual(RootElement));
        X = transformOffset.X;
        Y = transformOffset.Y;
    }

    internal void StartDrag(Point positionInElementCoordinates)
    {
        _relativePosition = positionInElementCoordinates;
        AssociatedObject.CaptureMouse();
        AssociatedObject.MouseMove += OnMouseMove;
        AssociatedObject.LostMouseCapture += OnLostMouseCapture;
        AssociatedObject.AddHandler(UIElement.MouseLeftButtonUpEvent,
            new MouseButtonEventHandler(OnMouseLeftButtonUp), false);
    }

    internal void HandleDrag(Point newPositionInElementCoordinates)
    {
        var point = TransformAsVector(AssociatedObject.TransformToVisual(RootElement),
            newPositionInElementCoordinates.X - _relativePosition.X,
            newPositionInElementCoordinates.Y - _relativePosition.Y);
        _settingPosition = true;
        ApplyTranslation(point.X, point.Y);
        UpdatePosition();
        _settingPosition = false;
    }

    internal void EndDrag()
    {
        AssociatedObject.MouseMove -= OnMouseMove;
        AssociatedObject.LostMouseCapture -= OnLostMouseCapture;
        AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonUpEvent,
            new MouseButtonEventHandler(OnMouseLeftButtonUp));
    }

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        StartDrag(e.GetPosition(AssociatedObject));
        DragBegun?.Invoke(this, e);
    }

    private void OnLostMouseCapture(object sender, MouseEventArgs e)
    {
        EndDrag();
        DragFinished?.Invoke(this, e);
    }

    private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        AssociatedObject.ReleaseMouseCapture();
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        HandleDrag(e.GetPosition(AssociatedObject));
        Dragging?.Invoke(this, e);
    }

    private static bool RectContainsRect(Rect rect1, Rect rect2)
    {
        if (rect1.IsEmpty || rect2.IsEmpty || rect1.X > rect2.X || rect1.Y > rect2.Y ||
            rect1.X + rect1.Width < rect2.X + rect2.Width)
            return false;
        return rect1.Y + rect1.Height >= rect2.Y + rect2.Height;
    }

    private static Point TransformAsVector(GeneralTransform transform, double x, double y)
    {
        var point1 = transform.Transform(new Point(0.0, 0.0));
        var point2 = transform.Transform(new Point(x, y));
        return new Point(point2.X - point1.X, point2.Y - point1.Y);
    }

    private static Point GetTransformOffset(GeneralTransform transform)
    {
        return transform.Transform(new Point(0.0, 0.0));
    }

    protected override void OnAttached()
    {
        AssociatedObject.AddHandler(UIElement.MouseLeftButtonDownEvent,
            new MouseButtonEventHandler(OnMouseLeftButtonDown), false);
    }

    protected override void OnDetaching()
    {
        AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonDownEvent,
            new MouseButtonEventHandler(OnMouseLeftButtonDown));
    }
}
